/*
===========================================================================
Copyright (C) 2009-2010 Mercury

This file is part of Tremulous.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "g_local.h"

// How big the bbox for the portals should be.
#define BBOX 15.0f

// How far to trace out.
#define SCAN_RANGE 150.0f

/*
===============
G_Portal_Think
This is the portals think function. This is used to make sure portals don't stay living after their parents are gone...
===============
*/
static void G_Portal_Think( gentity_t *portal )
{
	// If the client isn't holding the portal gun, or if it isn't connected, clear portals
	if( portal->parent->client->ps.weapon != WP_PORTAL || portal->parent->client->pers.connected != CON_CONNECTED )
	{
		G_Portal_Clear( portal->parent, 0);
		return;
	}
	
	// If the portals livetime is up, kill self
	if( portal->portalLiveTime <= level.time )
	{
		if( portal->parent->portal[0] == portal )
		{
			G_Portal_Clear( portal->parent, 1);
		}
		else if( portal->parent->portal[1] == portal )
		{
			G_Portal_Clear( portal->parent, 2);
		}
		return;
	}
	
	// Make sure we keep thinking...
	portal->nextthink = level.time;
	return;
}

/*
===============
G_Portal_Clear
This is the portal clear function. This is used to clear any living portals...
===============
*/
void G_Portal_Clear( gentity_t *parent, int portal )
{
	if( !parent->client )
	return;

	// If we want to clear both portals...
	if( portal == 0 )
	{
		// If there is a first portal already, free its entity and NULL out its memory
		if( parent->portal[0] )
		{
			G_FreeEntity( parent->portal[0] );
			parent->portal[0] = NULL;
		}
		
		// If there is a second portal already, free its entity and NULL out its memory
		if( parent->portal[1] )
		{
			G_FreeEntity( parent->portal[1] );
			parent->portal[1] = NULL;
		}
	}
	
	// If there is a portal already, and it isn't NULLed out, kill it and make it NULL
	else if( portal == 1 && parent->portal[0] )
	{
		G_FreeEntity( parent->portal[0] );
		parent->portal[0] = NULL;
	}
	
	// If there is a portal already, and it isn't NULLed out, kill it and make it NULL
	else if( portal == 2 && parent->portal[1] )
	{
		G_FreeEntity( parent->portal[1] );
		parent->portal[1] = NULL;
	}
	
	// If there is nothing to do, return!
	return;
}

/*
===============
G_Portal_Touch
The portal touch function. This is used to do traces and to teleport the clients using the portal!
===============
*/
static void G_Portal_Touch(gentity_t *self, gentity_t *other, trace_t *trace)
{
	gentity_t	*mate, *traceEnt;
	int		i, velocity;
	vec3_t		origin, dir, end, mins, maxs, angles;
	trace_t		tr, tr2;

	// If it isn't a client, ignore it
	if (!other->client)
		return;
	
	// Make sure we clear the mate...
	mate = NULL;
	
	// Simple function to see which portal is which
	if( self->parent->portal[0] == self )
	{
		mate = self->parent->portal[1];
	}
	else if( self->parent->portal[1] == self )
	{
		mate = self->parent->portal[0];
	}
	
	// If there isn't a mate, don't go on!
	if( !mate )
	return;
	
	// Grab some portal info for tracing and velocity checks
	VectorCopy(mate->r.currentOrigin, origin);
	VectorCopy(mate->s.angles2, dir);
	
	// My big ugly loop for dynamic tracing and teleportation of the client
	for( i = BBOX; i < SCAN_RANGE; i++ )
	{
		// Scale the trace to only go so far...
		VectorMA(origin, i, dir, end);
		
		// Two traces for anti-glitching reasons, these traces see how far forward I can go
		trap_Trace(&tr, origin, NULL, NULL, end, other->s.number, MASK_PLAYERSOLID);
		trap_Trace(&tr2, origin, other->r.mins, other->r.maxs, end, other->s.number, MASK_PLAYERSOLID);
		
		// Grab bbox info for EntityContact function
		VectorAdd( tr.endpos, other->r.mins, mins );
		VectorAdd( tr.endpos, other->r.maxs, maxs );
		
		// If they are both clear, move on the the client teleportation
		if( tr.fraction == 1.0f && tr2.fraction == 1.0f && !trap_EntityContact( mins, maxs, mate ) )
		{
			
			// Make sure the client doesn't mess with any map objects
			trap_UnlinkEntity( other );
			
			// Set the client origin..
			G_SetOrigin( other, tr.endpos );
			VectorCopy( tr.endpos, other->client->ps.origin );
			
			// Clear any unlagged info
			G_UnlaggedClear( other );
			
			// Set the teleportation flag, so the client doesn't look as if he/she is warping all over the map
			other->client->ps.eFlags ^= EF_TELEPORT_BIT;
			
			// Grab the velocity of a player without direction
			velocity = VectorLength(other->client->ps.velocity) / 1.1;
			
			// Make sure we don't overdo the viewangle change
			if( other->lastPortalTime < level.time && dir[2] != 1 && dir[2] != -1 )
			{
				vectoangles(dir, angles);
				G_SetClientViewAngle( other, angles);
			}
			
			// Set the direction using the velocity and the length taken
			VectorScale(dir, velocity, other->client->ps.velocity);
			
			// Set last portal time..used for viewangle functions
			other->lastPortalTime = level.time + 300;
			
			// Link the entity back to the world
			trap_LinkEntity( other );
			
			// All is well!!
			return;
		}
		
		// Set the entity picked up on the trace...
		traceEnt = &g_entities[ tr.entityNum ];
		
		// If the traceEnt is a client...
		if( traceEnt->client )
		{
			// Push the client out of the way!
			VectorScale(dir, 250, traceEnt->client->ps.velocity);
			return;
		}
	}
}

/*
===============
G_Portal_Create
This is used to create a portal. Here we will spawn it's entity, define its model, and give it a bbox!
===============
*/
void G_Portal_Create(gentity_t *parent, vec3_t origin, vec3_t normal, int portal)
{
	gentity_t	*portalent;
	vec3_t		end, maxs, mins, tmins, tmaxs;
	trace_t		tr;
	
	// If the parent isn't a client...return
	if( !parent->client )
	return;
	
	// Set the bbox...
	VectorSet( mins, -BBOX, -BBOX, -BBOX );
	VectorSet( maxs,  BBOX,  BBOX,  BBOX );
	
	// Clear any existing portals!
	G_Portal_Clear( parent, portal + 1);
		
	// Trace 1 game unit forward, to not mix in walls!
	VectorMA(origin, 1, normal, end);
	trap_Trace(&tr, origin, NULL, NULL, end, -1, MASK_SHOT);
		
	// Get the location + bbox for EntityContact info
	VectorAdd( tr.endpos, mins, tmins );
	VectorAdd( tr.endpos, maxs, tmaxs );
		
	// If we're trying to make the first portal, and we have a second...
	if( portal == 0 && parent->portal[1] )
	{
		// This is only to help EntityContact
		parent->portal[1]->r.contents = CONTENTS_SOLID;

		// If we're in contact with the mate, return
		if( trap_EntityContact( tmins, tmaxs, parent->portal[1] ) )
		{
			// Set the mates contents back to normal
			parent->portal[1]->r.contents = CONTENTS_TRIGGER;
			return;
		}
		
		// If we fail, set it back to normal
		parent->portal[1]->r.contents = CONTENTS_TRIGGER;
	}
		
	// If we're trying to make the second portal, and we have a first...
	if( portal == 1 && parent->portal[0] )
	{
		// This is only to help EntityContact
		parent->portal[0]->r.contents = CONTENTS_SOLID;

		// If we're in contact with the mate, return
		if( trap_EntityContact( tmins, tmaxs, parent->portal[0] ) )
		{
			// Set the mates contents back to normal
			parent->portal[0]->r.contents = CONTENTS_TRIGGER;
			return;
		}
		
		// If we fail, set it back to normal
		parent->portal[0]->r.contents = CONTENTS_TRIGGER;
	}
		
	// Create the portal entity
	portalent = G_Spawn();
	
	// Set the parent, this will be use to track other portals made by this client
	portalent->parent = parent;

	// Set entity type as general so we can define a model
	portalent->s.eType = ET_GENERAL;
	
	// Set the portals contents, this is used to help with bbox info
	portalent->r.contents = CONTENTS_TRIGGER;
	
	// Define the touch, and think function, when a portal is touched, teleportation time!
	portalent->touch = G_Portal_Touch;
	portalent->think = G_Portal_Think;
	portalent->nextthink = level.time;
	
	// Define model...
	portalent->s.modelindex = G_ModelIndex( "models/fx/portal/portal.md3" );

	// Help with ET_GENERAL stuff
	portalent->s.pos.trType = TR_STATIONARY;
	portalent->s.pos.trTime = level.time;
	
	// Set the surface face, this is also used to check which direction we will be tracing
	vectoangles( normal, portalent->s.angles);
	VectorCopy(normal, portalent->s.angles2);
		
	// Set the live time for portals!
	portalent->portalLiveTime = level.time + 30000;
		
	// Set the portals origin
	G_SetOrigin(portalent, tr.endpos);
		
	// Add bbox
	VectorCopy( mins, portalent->r.mins );
	VectorCopy( maxs, portalent->r.maxs );
	
	// Link entity to the world!
	trap_LinkEntity( portalent );
		
	parent->portal[ portal ] = portalent;
}
